Skip to content

fix(cli): exit after capture completes - #94

Merged
melonamin merged 2 commits into
mainfrom
fix/cli-exit
Sep 27, 2026
Merged

melonamin merged 2 commits into
mainfrom
fix/cli-exit

Conversation

@melonamin

Copy link
Copy Markdown
Member

Root cause

The 2.0 rewrite dropped the Darwin.exit(success ? 0 : 1) call that followed capture() in the CLI (1.x had it), and capture() stopped returning a success flag. After a capture the Task wrapping trex.main() completed but nothing terminated the process, so it parked in dispatchMain() forever. dispatchMain() also terminates the main thread, which is what produced the CFRunLoopError_MainThreadHasExited warning.

Fix

  • Restore capture()'s Bool result in TRexCore (@discardableResult, so all app call sites are unchanged)
  • CLI throws ExitCode.failure when capture produces no text (user cancelled, no text recognized, image unreadable), so trex exits 1; parse/stdin errors already exited via ArgumentParser
  • Exit 0 explicitly after trex.main() returns
  • Replace dispatchMain() with RunLoop.main.run() so the main thread stays alive to service @MainActor work; this also removes the run loop warning

Verification

  • trex --help exits 0
  • trex -i < /dev/null exits 1 with error message
  • trex -i < image.png prints recognized text, exits 0, no run loop warning; two back-to-back invocations both work (the Alfred case)
  • xcodebuild -scheme "TRex CLI" and -scheme TRex Debug builds succeed
  • swift test --package-path Packages/TRexCore: 21 tests pass, including two new regression tests for the restored capture() return value; the exit logic itself lives in the CLI entry point, which has no test target

Interactive screen capture (trex with no flags) was not exercised here since it needs screen recording permission and a user selection; it goes through the same capture()/exit path.

Fixes #93

The 2.0 rewrite dropped the Darwin.exit call that followed capture in
the CLI, and capture() no longer reported success. After a capture the
process parked in dispatchMain() forever, so every invocation had to be
killed manually and repeated Alfred invocations broke. dispatchMain()
also terminated the main thread, producing the CFRunLoop "main thread
has exited" warning on startup.

Restore capture()'s Bool result (discardable, so app call sites are
unchanged), make the CLI exit 1 when capture produces no text, exit 0
once the command finishes, and keep the main thread alive with
RunLoop.main.run() instead of dispatchMain().
Review findings on #94: runShortcut spawned its child inside a detached task, so the CLI's new prompt exit could terminate the process before the shortcut ever ran; the spawn is now synchronous with cleanup in a termination handler. captureSingle and captureMultiRegion returned success for whitespace-only OCR results, so the CLI exited 0 with no text; both now treat empty results as failure.
@melonamin
melonamin merged commit e21cb71 into main Sep 27, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Alfred integration doesn't work anymore (no CLI tool inside .app anymore)

1 participant